﻿using LCL.Network.IMessage;
using LCL.Threading;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace LCL.Network
{
    /// <summary>
    /// 支持长连接，短连接两个模式的通用客户端基类
    /// </summary>
    /// <typeparam name="TNetMessage">指定了消息的解析规则</typeparam>
    /// <typeparam name="TTransform">指定了数据转换的规则</typeparam>
    public class NetworkDoubleBase<TNetMessage, TTransform> : NetworkBase
        where TNetMessage : INetMessage, new()
        where TTransform : IByteTransform, new()
    {
        #region Constructor
        /// <summary>
        /// 默认的无参构造函数
        /// </summary>
        public NetworkDoubleBase()
        {
            InteractiveLock = new SimpleHybirdLock();                    // 实例化数据访问锁
            byteTransform = new TTransform();                            // 实例化数据转换规则
            connectionId = Utils.GetUniqueStringByGuidAndRandom();
        }
        #endregion
        #region Private Member
        private TTransform byteTransform;                // 数据变换的接口
        private string ipAddress = "127.0.0.1";          // 连接的IP地址
        private int port = 10000;                        // 端口号
        private int connectTimeOut = 10000;              // 连接超时时间设置
        private int receiveTimeOut = 10000;              // 数据接收的超时时间
        private bool isPersistentConn = false;           // 是否处于长连接的状态
        private SimpleHybirdLock InteractiveLock;        // 一次正常的交互的互斥锁
        private bool IsSocketError = false;              // 指示长连接的套接字是否处于错误的状态
        private bool isUseSpecifiedSocket = false;       // 指示是否使用指定的网络套接字访问数据
        private string connectionId = string.Empty;      // 当前连接
        #endregion
        #region Public Member
        /// <summary>
        /// 当前客户端的数据变换机制，当你需要从字节数据转换类型数据的时候需要。
        /// </summary>
        public TTransform ByteTransform
        {
            get { return byteTransform; }
            set { byteTransform = value; }
        }
        /// <summary>
        /// 获取或设置连接的超时时间
        /// </summary>
        /// <remarks>
        /// 不适用于异形模式的连接。
        /// </remarks>
        public int ConnectTimeOut
        {
            get { return connectTimeOut; }
            set { connectTimeOut = value; }
        }
        /// <summary>
        /// 获取或设置接收服务器反馈的时间，如果为负数，则不接收反馈
        /// </summary>
        /// <remarks>
        /// 超时的通常原因是服务器端没有配置好，导致访问失败，为了不卡死软件，所以有了这个超时的属性。
        /// </remarks>
        public int ReceiveTimeOut
        {
            get { return receiveTimeOut; }
            set { receiveTimeOut = value; }
        }
        /// <summary>
        /// 获取或是设置服务器的IP地址
        /// </summary>
        /// <remarks>
        /// 最好实在初始化的时候进行指定，当使用短连接的时候，支持动态更改，切换；当使用长连接后，无法动态更改
        /// </remarks>
        public string IpAddress
        {
            get
            {
                return ipAddress;
            }
            set
            {
                if (!string.IsNullOrEmpty(value))
                {
                    IPAddress address;
                    if (!IPAddress.TryParse(value, out address))
                    {
                        throw new Exception("Ip地址输入异常，格式不正确");
                    }
                    ipAddress = value;
                }
            }
        }
        /// <summary>
        /// 获取或设置服务器的端口号
        /// </summary>
        /// <remarks>
        /// 最好实在初始化的时候进行指定，当使用短连接的时候，支持动态更改，切换；当使用长连接后，无法动态更改
        /// </remarks>
        /// <example>
        /// 动态更改请参照IpAddress属性的更改。
        /// </example>
        public int Port
        {
            get
            {
                return port;
            }
            set
            {
                port = value;
            }
        }
        /// <summary>
        /// 当前连接的唯一ID号，默认为长度20的guid码加随机数组成，方便列表管理，也可以自己指定
        /// </summary>
        public string ConnectionId
        {
            get { return connectionId; }
            set { connectionId = value; }
        }
        /// <summary>
        /// 当前的异形连接对象，如果设置了异形连接的话
        /// </summary>
        /// <remarks>
        /// 具体的使用方法请参照Demo项目中的异形modbus实现。
        /// </remarks>
        public AlienSession AlienSession { get; set; }
        #endregion
        #region Public Method
        /// <summary>
        /// 在读取数据之前可以调用本方法将客户端设置为长连接模式，相当于跳过了ConnectServer的结果验证，对异形客户端无效
        /// </summary>
        public void SetPersistentConnection()
        {
            isPersistentConn = true;
        }
        #endregion
        #region Connect Close
        /// <summary>
        /// 切换短连接模式到长连接模式，后面的每次请求都共享一个通道
        /// </summary>
        /// <returns>返回连接结果，如果失败的话（也即Success为False），包含失败信息</returns>
        public Result ConnectServer()
        {
            isPersistentConn = true;
            Result result = new Result();
            // 重新连接之前，先将旧的数据进行清空
            if (CoreSocket != null) CoreSocket.Close();
            Result<Socket> rSocket = CreateSocketAndInitialication();
            if (!rSocket.Success)
            {
                IsSocketError = true;
                rSocket.Data = null;
                result.Message = rSocket.Message;
            }
            else
            {
                CoreSocket = (Socket)rSocket.Data;
                result.Success = true;
                LogNet.Debug(ToString(), "启动引擎");
            }
            return result;
        }
        /// <summary>
        /// 使用指定的套接字创建异形客户端
        /// </summary>
        /// <param name="session">异形客户端对象，查看<seealso cref="NetworkAlienClient"/>类型创建的客户端</param>
        /// <returns>通常都为成功</returns>
        /// <remarks>
        /// 不能和之前的长连接和短连接混用，详细参考 Demo程序 
        /// </remarks>
        public Result ConnectServer(AlienSession session)
        {
            isPersistentConn = true;
            isUseSpecifiedSocket = true;
            if (session != null)
            {
                AlienSession.Socket.Close();
                if (string.IsNullOrEmpty(ConnectionId))
                {
                    ConnectionId = session.DTU;
                }
                if (ConnectionId == session.DTU)
                {
                    CoreSocket = session.Socket;
                    IsSocketError = false;
                    AlienSession = session;
                    return InitializationOnConnect(session.Socket);
                }
                else
                {
                    IsSocketError = true;
                    return new Result();
                }
            }
            else
            {
                IsSocketError = true;
                return new Result();
            }
        }
        /// <summary>
        /// 在长连接模式下，断开服务器的连接，并切换到短连接模式
        /// </summary>
        /// <returns>关闭连接，不需要查看Success属性查看</returns>
        public Result ConnectClose()
        {
            Result result = new Result();
            isPersistentConn = false;
            InteractiveLock.Enter();
            // 额外操作
            result = ExtraOnDisconnect(CoreSocket);
            // 关闭信息
            if (CoreSocket != null) CoreSocket.Close();
            CoreSocket = null;
            InteractiveLock.Leave();
            LogNet.Debug(ToString(), "断开服务器的连接");
            return result;
        }
        #endregion
        #region Initialization And Extra
        /// <summary>
        /// 连接上服务器后需要进行的初始化操作
        /// </summary>
        /// <param name="socket">网络套接字</param>
        /// <returns>是否初始化成功，依据具体的协议进行重写</returns>
        protected virtual Result InitializationOnConnect(Socket socket)
        {
            return Result.CreateSuccessResult();
        }
        /// <summary>
        /// 在将要和服务器进行断开的情况下额外的操作，需要根据对应协议进行重写
        /// </summary>
        /// <param name="socket">网络套接字</param>
        /// <example>
        /// 目前暂无相关的示例，组件支持的协议都不用实现这个方法。
        /// </example>
        /// <returns>当断开连接时额外的操作结果</returns>
        protected virtual Result ExtraOnDisconnect(Socket socket)
        {
            return Result.CreateSuccessResult();
        }
        #endregion
        #region Core Communication
        /***************************************************************************************
         * 
         *    主要的数据交互分为4步
         *    1. 连接服务器，或是获取到旧的使用的网络信息
         *    2. 发送数据信息
         *    3. 接收反馈的数据信息
         *    4. 关闭网络连接，如果是短连接的话
         * 
         **************************************************************************************/
        /// <summary>
        /// 获取本次操作的可用的网络套接字
        /// </summary>
        /// <returns>是否成功，如果成功，使用这个套接字</returns>
        private Result<Socket> GetAvailableSocket()
        {
            if (isPersistentConn)
            {
                // 如果是异形模式
                if (isUseSpecifiedSocket)
                {
                    if (IsSocketError)
                    {
                        return new Result<Socket>("当前的连接不可用");
                    }
                    else
                    {
                        return Result.CreateSuccessResult(CoreSocket);
                    }
                }
                else
                {
                    // 长连接模式
                    if (IsSocketError || CoreSocket == null)
                    {
                        Result connect = ConnectServer();
                        if (!connect.Success)
                        {
                            IsSocketError = true;
                            return Result.CreateFailedResult<Socket>(connect);
                        }
                        else
                        {
                            IsSocketError = false;
                            return Result.CreateSuccessResult(CoreSocket);
                        }
                    }
                    else
                    {
                        return Result.CreateSuccessResult(CoreSocket);
                    }
                }
            }
            else
            {
                // 短连接模式
                return CreateSocketAndInitialication();
            }
        }
        /// <summary>
        /// 连接并初始化网络套接字
        /// </summary>
        /// <returns>带有socket的结果对象</returns>
        private Result<Socket> CreateSocketAndInitialication()
        {
            Result<Socket> result = CreateSocketAndConnect(new IPEndPoint(IPAddress.Parse(ipAddress), port), connectTimeOut);
            if (result.Success)
            {
                // 初始化
                Result initi = InitializationOnConnect(result.Data);
                if (!initi.Success)
                {
                    result.Data = null;
                    result.Success = initi.Success;
                    result.CopyErrorFromOther(initi);
                }
            }
            return result;
        }
        /// <summary>
        /// 在其他指定的套接字上，使用报文来通讯，传入需要发送的消息，返回一条完整的数据指令
        /// </summary>
        /// <param name="socket">指定的套接字</param>
        /// <param name="send">发送的完整的报文信息</param>
        /// <remarks>
        /// 无锁的基于套接字直接进行叠加协议的操作。
        /// </remarks>
        /// <returns>接收的完整的报文信息</returns>
        public virtual Result<byte[]> ReadFromCoreServer(Socket socket, byte[] send)
        {
            Result<byte[], byte[]> read = ReadFromCoreServerBase(socket, send);
            if (!read.Success) return Result.CreateFailedResult<byte[]>(read);
            // 拼接结果数据
            byte[] Data = new byte[read.Data1.Length + read.Data2.Length];
            if (read.Data1.Length > 0) read.Data1.CopyTo(Data, 0);
            if (read.Data2.Length > 0) read.Data2.CopyTo(Data, read.Data1.Length);
            return Result.CreateSuccessResult(Data);
        }
        /// <summary>
        /// 使用底层的数据报文来通讯，传入需要发送的消息，返回一条完整的数据指令
        /// </summary>
        /// <param name="send">发送的完整的报文信息</param>
        /// <returns>接收的完整的报文信息</returns>
        /// <remarks>
        /// 本方法用于实现本组件还未实现的一些报文功能，例如有些modbus服务器会有一些特殊的功能码支持，
        /// 需要收发特殊的报文，详细请看示例
        /// </remarks>
        public Result<byte[]> ReadFromCoreServer(byte[] send)
        {
            var result = new Result<byte[]>();
            InteractiveLock.Enter();
            // 获取有用的网络通道，如果没有，就建立新的连接
            Result<Socket> resultSocket = GetAvailableSocket();
            if (!resultSocket.Success)
            {
                IsSocketError = true;
                if (AlienSession != null) AlienSession.IsStatusOk = false;
                InteractiveLock.Leave();
                result.CopyErrorFromOther(resultSocket);
                return result;
            }
            Result<byte[]> read = ReadFromCoreServer(resultSocket.Data, send);
            if (read.Success)
            {
                IsSocketError = false;
                result.Success = read.Success;
                result.Data = read.Data;
                result.Message = Result.SuccessMessage;
            }
            else
            {
                IsSocketError = true;
                if (AlienSession != null) AlienSession.IsStatusOk = false;
                result.CopyErrorFromOther(read);
            }
            InteractiveLock.Leave();
            if (!isPersistentConn) resultSocket.Data.Close();
            return result;
        }
        /// <summary>
        /// 使用底层的数据报文来通讯，传入需要发送的消息，返回最终的数据结果，被拆分成了头子节和内容字节信息
        /// </summary>
        /// <param name="socket">网络套接字</param>
        /// <param name="send">发送的数据</param>
        /// <returns>结果对象</returns>
        /// <remarks>
        /// 当子类重写InitializationOnConnect方法和ExtraOnDisconnect方法时，需要和设备进行数据交互后，
        /// 必须用本方法来数据交互，因为本方法是无锁的。
        /// </remarks>
        protected Result<byte[], byte[]> ReadFromCoreServerBase(Socket socket, byte[] send)
        {
            LogNet.Debug(ToString( ),"发送 : " +Utils.ByteToHexString( send, ' ' ) );
            TNetMessage netMsg = new TNetMessage
            {
                SendBytes = send
            };
            // 发送数据信息
            Result sendResult = Send(socket, send);
            if (!sendResult.Success)
            {
                socket.Close();
                return Result.CreateFailedResult<byte[], byte[]>(sendResult);
            }
            // 接收超时时间大于0时才允许接收远程的数据
            if (receiveTimeOut >= 0)
            {
                // 接收数据信息
                Result<TNetMessage> resultReceive = ReceiveMessage(socket, receiveTimeOut, netMsg);
                if (!resultReceive.Success)
                {
                    socket.Close();
                    return new Result<byte[], byte[]>("接收数据超时：" + receiveTimeOut);
                }
                var hex = Utils.SpliceTwoByteArray(resultReceive.Data.HeadBytes, resultReceive.Data.ContentBytes);
                Utils.ByteToHexString(hex, ' ');
                // Success
                return Result.CreateSuccessResult(resultReceive.Data.HeadBytes, resultReceive.Data.ContentBytes);
            }
            else
            {
                return Result.CreateSuccessResult(new byte[0], new byte[0]);
            }
        }
        #endregion
        #region Object Override
        /// <summary>
        /// 返回表示当前对象的字符串
        /// </summary>
        /// <returns>字符串信息</returns>
        public override string ToString()
        {
            return "NetworkDoubleBase<" + typeof(TNetMessage) + ", " + typeof(TTransform) + ">[" + IpAddress + ":" + Port + "]";
        }
        #endregion
    }
}
